package rpc

import (
	"github.com/golang/protobuf/proto"
	. "leaf/core/error"
	"leaf/core/log"
	. "leaf/core/msg"
	"leaf/core/network"
	"reflect"
	"sync"
	"time"
)

type RpcClient struct {
	sync.Mutex
	network.Conn
	ready      bool
	uid        string
	cid        uint32
	rpcCalls   map[uint32]*RpcCall
	rpcHandler map[string]*RpcHandler
	rpcConnect IRpcConnect
}

func NewRpcClient(uid string, connecter IRpcConnect) *RpcClient {
	r := new(RpcClient)
	r.rpcCalls = make(map[uint32]*RpcCall)
	r.rpcHandler = map[string]*RpcHandler{}
	r.uid = uid
	r.rpcConnect = connecter
	return r
}

func (r *RpcClient) GetUid() string {
	return r.uid
}

func (r *RpcClient) GetReady() bool {
	return r.ready
}

func (r *RpcClient) GetCallId() uint32 {
	r.Lock()
	r.cid++
	if r.cid == 0 {
		r.cid = 1
	}
	r.Unlock()
	return r.cid
}

func (r *RpcClient) Process(head *MessageHead, msgBytes []byte) {
	if head.Type == MessageType_Msg {
		msg := NewMessageFromBytes(head, msgBytes)
		if msg == nil {
			log.Error("client process err:request msg is nil")
			return
		}

		r.MsgProcess(msg)
		return
	}

	if head.Type == MessageType_Rpc {
		c, ok := r.rpcCalls[head.Index]
		if !ok {
			log.Error("client process err:rpc index not find, index:%v", head.Index)
			return
		}

		if err := c.Msg.Reply.Parse(head, msgBytes); err != nil {
			log.Error("client process err:%v", err)
			return
		}
		c.Done <- c
		return
	}

	log.Error("client process err:msg type err")
}

func (r *RpcClient) OnClose() {
	if r.rpcConnect != nil {
		r.rpcConnect.OnRpcDisconnect(r.uid)
	}
}

func (r *RpcClient) Destroy() {
	r.Conn.Close()
	r.Conn.Destroy()
}

func (r *RpcClient) SendMsg(msg *Message) {
	bytes, err := Marshal(msg)
	if err != nil {
		log.Debug("notify err: %v", err)
		return
	}
	err = r.Conn.WriteMsg(bytes)
	if err != nil {
		log.Error("write message error: %v", err)
	}
}

func (r *RpcClient) MsgProcess(msg *Message) {
	if msg.MsgId == uint16(RpcMsgType_Handlers) {
		for _, handler := range msg.Msg.(*RpcHandlerInfos).GetHandlers() {
			rpcHandler := &RpcHandler{
				RequestType: GetMessageType(uint16(handler.GetRequestId())),
				ReplyType:   GetMessageType(uint16(handler.GetReplyId())),
				RequestId:   uint16(handler.GetRequestId()),
				ReplyId:     uint16(handler.GetReplyId()),
			}
			if rpcHandler.RequestId != 0 && rpcHandler.RequestType == nil {
				continue
			}
			if rpcHandler.ReplyId != 0 && rpcHandler.ReplyType == nil {
				continue
			}
			r.rpcHandler[handler.GetMethod()] = rpcHandler
		}

		r.ready = true
		//eventx.EventTrigger(eventx.EventId_Rpc_Connect, r)
	}
}

func (r *RpcClient) Rpc(rpcType int32, method string, request proto.Message, reply proto.Message) *Error {
	if r.Conn == nil {
		log.Error("Rpc err:Conn is nil, method:%v", method)
		return Error_RpcCall_ConnIsNil
	}

	rpcHandler, ok := r.rpcHandler[method]
	if !ok {
		log.Error("Rpc err:handle not find, method:%v", method)
		return Error_RpcCall_HandlerNotFind
	}

	if rpcHandler.RequestType != nil && rpcHandler.RequestType != reflect.TypeOf(request) {
		log.Error("Rpc err:request type wrong, method:%v", method)
		return Error_RpcCall_RequestError
	}

	if rpcHandler.ReplyType != nil && rpcHandler.ReplyType != reflect.TypeOf(reply) {
		log.Error("Rpc err:reply type wrong, method:%v", method)
		return Error_RpcCall_RequestError
	}

	if rpcType == 0 {
		rpcMsg := NewRpcCall(method, 0, request, nil)
		r.SendMsg(rpcMsg.Msg.Request)
		return nil
	}

	uid := r.GetCallId()
	rpcMsg := NewRpcCall(method, uid, request, reply)
	r.rpcCalls[uid] = rpcMsg
	r.SendMsg(rpcMsg.Msg.Request)

	t := time.NewTimer(2 * time.Second)
	defer t.Stop()

	for {
		select {
		case <-rpcMsg.Done:
			delete(r.rpcCalls, uid)
			if err := GetError(rpcMsg.Msg.Reply.Code); err != nil {
				return ConvertError(err)
			}

			return nil
		case <-t.C:
			delete(r.rpcCalls, uid)
			log.Error("Rpc handle [%v] response time out", method)
			return Error_RpcCall_TimeOut
		}
	}
}

func (r *RpcClient) Call(method string, request proto.Message, reply proto.Message) *Error {
	return r.Rpc(1, method, request, reply)
}

func (r *RpcClient) Cast(method string, requset proto.Message) *Error {
	return r.Rpc(0, method, requset, nil)
}
